///////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2007-2009 ILOG, S.A.
//  All Rights Reserved.
//  The following is ILOG Source Code.  No modifications may be made to the  
//  ILOG Source Code.  Any usage of the ILOG Source Code is subject to 
//  the terms and conditions of the ILOG End User License Agreement   
//  applicable to this ILOG software product.
//
///////////////////////////////////////////////////////////////////////////////

package ilog.gauges.circular.renderers
{
  import flash.display.DisplayObject;
  import flash.display.Graphics;
  import flash.geom.Point;
  import flash.geom.Rectangle;
  import flash.text.TextFormat;
  
  import ilog.gauges.RectangleTickRenderer;
  import ilog.gauges.TickItem;
  import ilog.gauges.gaugesClasses.circular.CircularScaleRelatedRenderer;
  import ilog.utils.CSSUtil;
  import ilog.utils.InstanceCache;
  import ilog.utils.MathUtil;
  import ilog.utils.RectangleUtil;
  
  import mx.charts.chartClasses.GraphicsUtilities;
  import mx.core.ClassFactory;
  import mx.core.ContextualClassFactory;
  import mx.core.FlexShape;
  import mx.core.IDataRenderer;
  import mx.core.IFactory;
  import mx.core.IFlexDisplayObject;
  import mx.core.UIComponent;
  import mx.core.UITextField;
  import mx.styles.CSSStyleDeclaration;

  /**
   * Alpha level of the color defined by the <code>backgroundColor</code>
   * property.
   * Valid values range from 0.0 to 1.0.
   *  
   * @default 1.0
   */
  [Style(name="backgroundAlpha", type="Number", inherit="no")]
  
  /**
   * Background color of the <code>CircularScaleRenderer</code>. The background
   * is drawn only under the tick area of the scale, not the label area. The <code>undefined</code> 
   * value means no background is drawn.
   * 
   * @default undefined   
   */
  [Style(name="backgroundColor", type="uint", format="Color", inherit="no")]

  /**
   * Specifies whether labels should appear along the scale. 
   *  
   * @default true 
   */
  [Style(name="showLabels", type="Boolean", inherit="no")]

  /**
   * Specifies whether major ticks should appear along the scale. 
   *  
   * @default true 
   */
  [Style(name="showMajorTicks", type="Boolean", inherit="no")]

  /**
   * Specifies whether minor ticks should appear along the scale. 
   *  
   * @default true 
   */
  [Style(name="showMinorTicks", type="Boolean", inherit="no")]

  /**
   * Specifies whether only minimum and maximum labels are shown. 
   *  
   * @default false 
   */
  [Style(name="onlyMinMaxLabels", type="Boolean", inherit="no")]
  
  /**
   * A style name to be used for the major tick renderers. Useful
   * for custom tick renderers that might use properties from this
   * style name. If the style name references a CSS selector the major
   * tick renderer must extend <code>UIComponent</code> or 
   * <code>TickRendererBase</code>.
   * 
   * @default null
   * @ilog.reviewed SH 5 May 08
   */
  [Style(name="majorTickStyleName", type="String", inherit="no")]

  /**
   * A style name to be used for the minor tick renderers. Useful
   * for custom tick renderers that might use properties from this
   * style name. If the style name references a CSS selector the minor
   * tick renderer must extend <code>UIComponent</code> or 
   * <code>TickRendererBase</code>.
   *
   * @default null   
   */
  [Style(name="minorTickStyleName", type="String", inherit="no")]    

  /**
   * You use the <code>CircularScaleRenderer</code> class to describe
   * the rendering of a gauge's scale. 
   * A scale renderer is responsible for rendering
   * the labels, major tick marks, and minor tick marks. 
   *
   * <p>Major ticks are controlled by the following properties:</p>
   * <ul>
   *     <li> <code>majorTickRenderer</code> </li>
   *     <li> <code>majorTickWidth</code> (expressed in pixels or as a percentage)</li>
   *     <li> <code>majorTickLength</code> (expressed in pixels or as a percentage)</li>
   * </ul>
   *      
   * <p>Minor ticks are controlled by the following properties:</p>
   * <ul>
   *     <li> <code>minorTickRenderer</code> </li>
   *     <li> <code>minorTickWidth</code> (expressed in pixels or as a percentage)</li>
   *     <li> <code>minorTickLength</code> (expressed in pixels or as a percentage)</li>
   * </ul>
   *
   * The placement of the ticks is controlled by the <code>tickPlacement</code> property.
   * You can control the style of the ticks by specifying a <code>minorTickStyleName</code> 
   * and a <code>majorTickStyleName</code>.
   *
   * Labels are controlled by the following properties:
   * <ul>
   *     <li> <code>labelRadius</code> (expressed in pixels or as a percentage)</li>
   *     <li> <code>labelFontSize</code> (expressed in pixels or as a percentage)</li>
   *     <li> <code>labelPlacement</code> (outside, inside, cross).</li>
   *     <li> <code>labelFunction</code></li>
   * </ul>
   *
   * <p><code>CircularScaleRenderer</code> objects inherit the text format of the labels 
   * from the enclosing gauge object.</p>
   *
   * The <code>CircularScaleRenderer</code> is able to render any circular-specific scale:
   * <code>CircularLinearScale</code>, <code>CircularLogScale</code>, and 
   * <code>CircularCategoryScale</code>.
   *
   * If no renderer is specified for major or minor ticks, the <code>RectangleTickRenderer</code> is used.
   *
   * @mxml
   *  
   * <p>The <code>&lt;ilog:CircularScaleRenderer&gt;</code> tag inherits all the properties
   * of its parent classes and adds the following properties:</p>
   *  
   * <pre>
   * &lt;ilog:CircularScaleRenderer
   *   <strong>Properties</strong>
   *   tickPlacement="inside|cross|outside"
   *   majorTickRenderer="<i>IFactory</i>"
   *   majorTickWidth=<i>"5%"</i>
   *   majorTickLength=<i>"10%"</i>
   *   minorTickRenderer="<i>IFactory</i>"
   *   minorTickWidth=<i>"3%"</i>
   *   minorTickLength=<i>"5%"</i>
   *   labelFunction="<i>Function</i>"
   *   labelRadius=<i>"100%"</i>
   *   labelRenderer="<i>UITextField</i>"
   *   labelFontSize=<i>"10%"</i>
   *   labelPlacement="inside|cross|outside"
   *   clipTicks="false|true"
   *    
   *   <strong>Styles</strong>
   *   showLabels="true|false"
   *   showMajorTicks="true|false"
   *   showMinorTicks="true|false" 
   *   onlyMinMaxLabels="false|true"
   *   majorTickStyleName="<i>No default</i>"
   *   minorTickStyleName="<i>No default</i>" 
   * /&gt;
   * </pre>
   *    
   * @see ilog.gauges.gaugesClasses.circular.CircularGaugeElement
   * @see ilog.gauges.RectangleTickRenderer
   * @includeExample examples/CircularScaleRendererExample.mxml -noswf
   * 
   */
  public class CircularScaleRenderer extends CircularScaleRelatedRenderer
  {
    private static var stylesInited:Boolean = initStyles();
     
    private static function initStyles():Boolean
    {
      var scaleStyle:CSSStyleDeclaration =
        CSSUtil.createSelector("CircularScaleRenderer");
        
      scaleStyle.defaultFactory = function():void {
        this.majorTickStyleName = null;
        this.minorTickStyleName = null;
        this.showLabels = true;
        this.onlyMinMaxLabels = false;
        this.backgroundAlpha = 1.0;
      }

      return true;
    }

    /**
     *  Constructor
     */        
    public function CircularScaleRenderer()
    {
      super();           
      _labelCache = new InstanceCache(UITextField, this);
    }

    /**
     *  @private 
     */
    private var _labelCache:InstanceCache;  

    /**
     *  @private 
     */
    private var _majorTicksCache:InstanceCache;

    /**
     *  @private 
     */        
    private var _minorTicksCache:InstanceCache;

    /**
     *  @private 
     */        
    private var _ticksHolder:UIComponent;

    /**
     *  @private 
     */        
    private var _clipTicks:Boolean = false;
    
    [Inspectable(category="Gauge")]    
    
    /**
     * Specifies whether the first and last major ticks are clipped so that 
     * no ticks are drawn before the position of the minimum value nor after the
     * position of the maximum value. This is particularly useful when you want
     * to use a <code>CircularBarRenderer</code> with this scale, as the bar 
     * rendering starts on the mimimum value and does not go beyond the maximum
     * value.  
     * 
     * @default false
     */
    public function get clipTicks():Boolean
    {
      return _clipTicks;
    }    
    
    /**
     * @private
     */     
    public function set clipTicks(value:Boolean):void
    {
      if (_clipTicks != value) {
        _clipTicks = value;
        invalidateDisplayList();  
      }
    }    

    /**
     * @private
     */
    protected override function createChildren():void
    {
      if (_ticksHolder == null) {
        _ticksHolder = new UIComponent();
        addChild(_ticksHolder);
      }
    }  

    /**
     *  @private 
     */    
    private var _percentMajorTickLength:Number = 10;

    [Inspectable(environment="none")]

    /**
     * Number that specifies the height of major ticks as a percentage
     * of the scale radius. Allowed values are 0-100.
     * 
     * @default 10
     */
    public function get percentMajorTickLength():Number
    {
      return _percentMajorTickLength;
    }

    /**
     *  @private 
     */
    public function set percentMajorTickLength(value:Number):void
    {
      _percentMajorTickLength = value;
      if (! isNaN(value))
        _majorTickLength = NaN;      
      invalidateDisplayList();
    }

    /**
     *  @private 
     */
    private var _majorTickLength:Number = NaN;

    [Bindable("majorTickLengthChanged")]
    [Inspectable(category="Gauge")]
    [PercentProxy("percentMajorTickLength")]

    /**
     * Number that specifies the height of major ticks in pixels. 
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>majorTickLength</code> attribute, such as <code>majorTickLength="5%"</code>,
     * but you cannot use a percentage value in the <code>majorTickLength</code>
     * property in ActionScript.
     * Use the <code>percentMajorTickLength</code> property instead.</p>
     *
     * @default NaN 
     */
    public function get majorTickLength():Number
    {
      return _majorTickLength;
    }

    /**
     * @private 
     */
    public function set majorTickLength(value:Number):void
    {
      _majorTickLength = value;
      if (! isNaN(value))
        _percentMajorTickLength = NaN;
      invalidateDisplayList();      
    }

    /**
     * @private 
     */
    private var _percentMinorTickLength:Number = 5;

    [Inspectable(environment="none")]

    /**
     * Number that specifies the height of minor ticks as a percentage
     * of the scale radius. Allowed values are 0-100.
     * 
     * @default 5
     */
    public function get percentMinorTickLength():Number
    {
      return _percentMinorTickLength;
    }

    /**
     * @private 
     */
    public function set percentMinorTickLength(value:Number):void
    {
      _percentMinorTickLength = value;
      if (! isNaN(value))
        _minorTickLength = NaN;      
      invalidateDisplayList();
    }

    /**
     *  @private 
     */
    private var _minorTickLength:Number = NaN;

    [Bindable("minorTickLengthChanged")]
    [Inspectable(category="Gauge")]
    [PercentProxy("percentMinorTickLength")]

    /**
     * Number that specifies the height of minor ticks in pixels. 
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>minorTickLength</code> attribute, such as <code>minorTickLength="5%"</code>,
     * but you cannot use a percentage value in the <code>minorTickLength</code>
     * property in ActionScript.
     * Use the <code>percentMinorTickLength</code> property instead.</p>
     * 
     * @default NaN 
     */
    public function get minorTickLength():Number
    {
      return _minorTickLength;
    }

    /**
     *  @private 
     */
    public function set minorTickLength(value:Number):void
    {
      _minorTickLength = value;
      if (! isNaN(value))
        _percentMinorTickLength = NaN;
      invalidateDisplayList();      
    }

    /**
     *  @private 
     */
    private var _percentMajorTickWidth:Number = 5;

    [Inspectable(environment="none")]

    /**
     * Number that specifies the width of major ticks as a percentage
     * of the scale radius. Allowed values are 0-100.
     * 
     * @default 5
     */
    public function get percentMajorTickWidth():Number
    {
      return _percentMajorTickWidth;
    }
   
    /**
     * @private 
     */
    public function set percentMajorTickWidth(value:Number):void
    {
      _percentMajorTickWidth = value;
      if (! isNaN(value))
        _majorTickWidth = NaN;      
      invalidateDisplayList();
    }

    /**
     *  @private 
     */
    private var _majorTickWidth:Number = NaN;

    [Bindable("majorTickWidthChanged")]
    [Inspectable(category="Gauge")]
    [PercentProxy("percentMajorTickWidth")]

    /**
     * Number that specifies the width of major ticks in pixels. 
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>majorTickWidth</code> attribute, such as <code>majorTickWidth="5%"</code>,
     * but you cannot use a percentage value in the <code>majorTickWidth</code>
     * property in ActionScript.
     * Use the <code>percentMajorTickWidth</code> property instead.</p>
     * 
     * @default NaN 
     */
    public function get majorTickWidth():Number
    {
      return _majorTickWidth;
    }

    /**
     *  @private 
     */
    public function set majorTickWidth(value:Number):void
    {
      _majorTickWidth = value;
      if (! isNaN(value))
        _percentMajorTickWidth = NaN;
      invalidateDisplayList();      
    }

    /**
     *  @private 
     */
    private var _percentMinorTickWidth:Number = 3;

    [Inspectable(environment="none")]

    /**
     * Number that specifies the width of minor ticks as a percentage
     * of the scale radius. Allowed values are 0-100.
     * 
     * @default 3
     */
    public function get percentMinorTickWidth():Number
    {
      return _percentMinorTickWidth;
    }

    /**
     *  @private 
     */
    public function set percentMinorTickWidth(value:Number):void
    {
      _percentMinorTickWidth = value;
      if (! isNaN(value))
        _minorTickWidth = NaN;      
      invalidateDisplayList();
    }

    /**
     *  @private 
     */
    private var _minorTickWidth:Number = NaN;

    [Bindable("minorTickWidthChanged")]
    [Inspectable(category="Gauge")]
    [PercentProxy("percentMinorTickWidth")]

    /**
     * Number that specifies the width of minor ticks in pixels. 
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>minorTickWidth</code> attribute, such as <code>minorTickWidth="5%"</code>,
     * but you cannot use a percentage value in the <code>minorTickWidth</code>
     * property in ActionScript.
     * Use the <code>percentMinorTickWidth</code> property instead.</p>
     * 
     * @default NaN 
     */
    public function get minorTickWidth():Number
    {
      return _minorTickWidth;
    }

    /**
     *  @private 
     */
    public function set minorTickWidth(value:Number):void
    {
      _minorTickWidth = value;
      if (! isNaN(value))
        _percentMinorTickWidth = NaN;      
      invalidateDisplayList();      
    }

    /**
     *  @private 
     */
    private var _percentLabelRadius:Number = 85;

    [Inspectable(environment="none")]

    /**
     * Number that specifies the radius used for the positioning of the labels as a percentage
     * of the scale radius. Allowed values are 0-100.
     * 
     * @default 85
     */
    public function get percentLabelRadius():Number
    {
      return _percentLabelRadius;
    }

    /**
     *  @private 
     */
    public function set percentLabelRadius(value:Number):void
    {
      _percentLabelRadius = value;
      if (! isNaN(value))
        _labelRadius = NaN;  
      invalidateDisplayList();
    }

    /**
     *  @private 
     */
    private var _labelRadius:Number = NaN;

    [Bindable("labelRadiusChanged")]
    [Inspectable(category="Gauge")]
    [PercentProxy("percentLabelRadius")]

    /**
     * Number that specifies the radius used for the positioning of the labels in pixels. 
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>labelRadius</code> attribute, such as <code>labelRadius="5%"</code>,
     * but you cannot use a percentage value in the <code>labelRadius</code>
     * property in ActionScript.
     * Use the <code>percentLabelRadius</code> property instead.</p>
     * The <code>labelPlacement</code> property defines how labels are placed
     * compared to the circle defined by this radius.
     * 
     * @default NaN 
     */
    public function get labelRadius():Number
    {
      return _labelRadius;
    }

    /**
     *  @private 
     */
    public function set labelRadius(value:Number):void
    {
      _labelRadius = value;
      if (! isNaN(value))
        _percentLabelRadius = NaN;      
      invalidateDisplayList();
    }

    /**
     *  @private 
     */
    private var _percentLabelFontSize:Number = 10;

    [Inspectable(environment="none")]

    /**
     * Number that specifies the font size of labels as a percentage
     * of the scale radius. Allowed values are 0-100.
     * If <code>labelRenderer</code> is set, this property is not used. 
     * 
     * @default 10
     * @ilog.reviewed SH 27 July 08
     */
    public function get percentLabelFontSize():Number
    {
      return _percentLabelFontSize;
    }

    /**
     *  @private 
     */
    public function set percentLabelFontSize(value:Number):void
    {
      _percentLabelFontSize = value;
      if (! isNaN(value))
        _labelFontSize = NaN;      
      invalidateDisplayList();
    }

    /**
     *  @private 
     */
    private var _labelFontSize:Number = NaN;

    [Bindable("labelFontSizeChanged")]
    [Inspectable(category="Style")]
    [PercentProxy("percentLabelFontSize")]

    /**
     * Number that specifies the font size of labels in pixels.
     * If <code>labelRenderer</code> is set, this property is not used. 
     *
     * <p><strong>Note:</strong> You can specify a percentage value in the MXML
     * <code>labelFontSize</code> attribute, such as <code>labelFontSize="5%"</code>,
     * but you cannot use a percentage value in the <code>labelFontSize</code>
     * property in ActionScript.
     * Use the <code>percentLabelFontSize</code> property instead.</p>
     * 
     * @default NaN 
     * @ilog.reviewed SH 27 July 08
     */
    public function get labelFontSize():Number
    {
      return _labelFontSize;
    }

    /**
     *  @private 
     */
    public function set labelFontSize(value:Number):void
    {
      _labelFontSize = value;
      if (! isNaN(value))
        _percentLabelFontSize = NaN;      
      invalidateDisplayList();      
    }

    /**
     *  @private
     *  Storage for the labelFunction property.
     */
    private var _labelFunction:Function = null;

    [Inspectable(category="Gauge")]
    
    /**
     * The <code>Function</code> called to format scale values for display as labels.
     * A <code>labelFunction</code> has the following signature:
     * <pre>
     *  function labelFunction(item:TickItem):String
     * </pre>
     * 
     * @default null 
     */
    public function get labelFunction():Function
    {
      return _labelFunction;
    }
    
    /**
     *  @private
     */
    public function set labelFunction(value:Function):void
    {
      _labelFunction = value;
    }

    /**
     *  @private 
     */
    private var _tickPlacement:String = "inside";
    
    [Inspectable(category="Gauge", enumeration="inside,outside,cross")]    

    /**
     * Specifies where to draw the tick marks (major and minor ticks). Options are:
     *  <ul>
     *   <li><code>inside</code> -
     *   Draws tick marks inside the radius defined by <code>radius</code>.</li>
     *
     *   <li><code>outside</code> -
     *   Draws tick marks outside the radius defined by <code>radius</code>.</li>
     *
     *   <li><code>cross</code> -
     *   Draws tick marks across the radius defined by <code>radius</code>.</li>
     * </ul>
     * 
     * @default "inside"
     */    
    public function get tickPlacement():String
    {
      return _tickPlacement;
    }

    /**
     *  @private 
     */
    public function set tickPlacement(value:String):void
    {
      _tickPlacement = value;
      invalidateDisplayList();
    }

    /**
     *  @private 
     */
    private var _labelPlacement:String = "inside";
    
    [Inspectable(category="Gauge", enumeration="inside,outside,cross")]    

    /**
     * Specifies where to draw the labels. Options are:
     * <ul>
     *   <li><code>inside</code> -
     *   Draws the labels inside the radius defined by <code>labelRadius</code>.</li>
     *
     *   <li><code>outside</code> -
     *   Draws the labels outside the radius defined by <code>labelRadius</code>.</li>
     *
     *   <li><code>cross</code> -
     *   Draws the labels across the radius defined by <code>labelRadius</code>.</li>
     * </ul>
     * 
     * @default "inside"
     */      
    public function get labelPlacement():String
    {
      return _labelPlacement;
    }

    /**
     *  @private 
     */   
    public function set labelPlacement(value:String):void
    {
      _labelPlacement = value;
      invalidateDisplayList();
    }        

    /**
     *  @private 
     */   
    private function defaultLabelFunction(item:TickItem):String
    {
      if (item.value is Number)
        return MathUtil.truncate(Number(item.value), 2).toString();
      if (item.value is String)
        return String(item.value);
      return "";
    }

    /**
     *  @private
     */
    override protected function commitProperties():void
    {
      super.commitProperties();
      textFieldFactory =  new ContextualClassFactory(UITextField, moduleFactory);
      if (labelRenderer == null)
      {
          _labelCache.count = 0;
          _labelCache.factory = textFieldFactory;
      }
    }

    private static var textFieldFactory:ContextualClassFactory;

    private var _labelRenderer:IFactory = null;
    
    [Inspectable(category="General")]
    
    /** 
     *  A reference to the factory used to render the scale labels. 
     *  This type must implement the <code>IDataRenderer</code>
     *  and <code>IFlexDisplayObject</code> interfaces.
     *  <p>The <code>CircularScaleRenderer</code> will create one instance of this 
     *  class for each major tick on the scale.
     *  The <code>data</code> property of the <code>labelRenderer</code> is assigned 
     *  a String object containing the label to be rendered.</p>
     *  @since ILOG Elixir 2.0
     *  @ilog.reviewed SH 27 July 08
     */
    public function get labelRenderer():IFactory
    {
        return _labelRenderer;
    }

    /**
     *  @private
     */ 
    public function set labelRenderer(value:IFactory):void
    {
        if (_labelRenderer == value)
            return;
        _labelRenderer = value;

        _labelCache.count = 0;
        if (value == null) 
          _labelCache.factory = textFieldFactory;
        else 
          _labelCache.factory = _labelRenderer;
        invalidateDisplayList();
    }

    /**
     *  @private 
     */       
    private var _majorTickRenderer:IFactory = new ClassFactory(RectangleTickRenderer);
    
    [Inspectable(category="Gauge")]
    
    /**
     * A factory that represents the class used by the scale to draw major ticks on the scale. 
     * This class is instantiated once for each tick in the scale. Classes used as a 
     * <code>majorTickRenderer</code> must implement the <code>IFlexDisplayObject</code>, 
     * <code>ISimpleStyleClient</code>, and, optionally, <code>IDataRenderer</code> interfaces. 
     * 
     * @default ilog.gauges.RectangleTickRenderer
     */
    public function get majorTickRenderer():IFactory 
    {
      return _majorTickRenderer;
    }

    /**
     *  @private 
     */   
    public function set majorTickRenderer(renderer:IFactory):void 
    {
      _majorTickRenderer = renderer;
      invalidateDisplayList();
    }

    /**
     *  @private 
     */       
    private var _minorTickRenderer:IFactory = new ClassFactory(RectangleTickRenderer);
    
    [Inspectable(category="Gauge")]
    
    /**
     * A factory that represents the class used by the scale to draw minor ticks on the scale. 
     * This class is instantiated once for each tick in the scale. Classes used as a 
     * <code>minorTickRenderer</code> must implement the <code>IFlexDisplayObject</code>, 
     * <code>ISimpleStyleClient</code>, and, optionally, <code>IDataRenderer</code> interfaces. 
     * 
     * @default ilog.gauges.RectangleTickRenderer
     */
    public function get minorTickRenderer():IFactory
    {
      return _minorTickRenderer;
    }

    /**
     *  @private 
     */   
    public function set minorTickRenderer(renderer:IFactory):void 
    {
      _minorTickRenderer = renderer;
      invalidateDisplayList();
    }        

    /**
     *  @private 
     */   
    private function getLabelRadius():Number
    {     
      return isNaN(labelRadius) ? getRadius() * percentLabelRadius / 100 : labelRadius;
    }

    /**
     *  @private 
     */   
    private function getLabelFontSize():Number
    {
      return isNaN(labelFontSize) ? getRadius() * percentLabelFontSize /100 : labelFontSize;
    }

    /**
     *  @private 
     */       
    private function getMajorTickWidth():Number
    {
      var ret:Number;
      if (isNaN(majorTickWidth))
        ret = getRadius() * percentMajorTickWidth / 100;
      else
        ret = majorTickWidth;
      return ret;
    }

    /**
     *  @private 
     */   
    private function getMinorTickWidth():Number
    {
      var ret:Number;
      if (isNaN(minorTickWidth))
        ret = getRadius() * percentMinorTickWidth / 100;
      else
        ret = minorTickWidth;
      return ret;
    }

    /**
     *  @private 
     */   
    private function getMajorTickLength():Number
    {
      var ret:Number;
      if (isNaN(majorTickLength))
        ret = getRadius() * percentMajorTickLength / 100;
      else
        ret = majorTickLength;
      return ret;
    }

    /**
     *  @private 
     */   
    private function getMinorTickLength():Number
    {
      var ret:Number;
      if (isNaN(minorTickLength))
        ret = getRadius() * percentMinorTickLength / 100;
      else
        ret = minorTickLength;
      return ret;
    }

    /**
     *  @private 
     */   
    private function createLabels(items:Array, onlyMinMax:Boolean = false):void
    {
      var labels:Array;
      var lF:Function = labelFunction != null ? labelFunction : defaultLabelFunction;
      var dataField:String = _labelRenderer ? "data" : "text";
      if (onlyMinMax)
      {
        _labelCache.count = 2;
        labels = _labelCache.instances;
        labels[0][dataField] = lF(items[i]);
        labels[1][dataField] = lF(items[items.length - 1]);
      } 
      else
      {
        _labelCache.count = items.length;
        labels = _labelCache.instances;
        for (var i:int = 0; i < items.length; i++)
          labels[i][dataField] = lF(items[i]);
      }
    }

    /**
     *  @private 
     */   
    private function distance(x1:Number, y1:Number, x2:Number, y2:Number):Number
    {
      return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
    }
  
    private function polar(ox:Number, oy:Number, ang:Number, rad:Number):Point
    {
      return new Point(ox + Math.cos(ang) * rad, oy - Math.sin(ang) * rad);
    }

    private function circlesIntersection(x0:Number, y0:Number, x1:Number, y1:Number, r0:Number, r1:Number):Array
    {
      var n:Number = (r1 * r1 - r0 * r0 - x1 * x1 + x0 * x0 - y1 * y1 + y0 * y0)/(2 * (y0 - y1));
      var a:Number = (x0 - x1)/(y0 - y1);
      var b:Number = 2 * y0 * a - 2 * n * a - 2 * x0;
      a *= a;
      a += 1;
      var c:Number = x0 * x0 + y0 * y0 + n * n - r0 * r0 - 2 * y0 * n;
      var d:Number = Math.sqrt(b * b - 4 * a * c);
      var p0:Point = new Point();
      var p1:Point = new Point();
      p0.x = (-b + d) / (2 * a);
      p1.x = (-b - d) / (2 * a);
      p0.y = n - p0.x * ((x0 - x1)/(y0 - y1));
      p1.y = n - p1.x * ((x0 - x1)/(y0 - y1));
      return [p0, p1];
    } 

    /**
     * Draws the scale.
     *
     * @param unscaledWidth Specifies the width of the component, in pixels,
     * in the component's coordinates, regardless of the value of the
     * <code>scaleX</code> property of the component.
     *
     * @param unscaledHeight Specifies the height of the component, in pixels,
     * in the component's coordinates, regardless of the value of the
     * <code>scaleY</code> property of the component. 
     */       
    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
      super.updateDisplayList(unscaledWidth, unscaledHeight);

      if (!scale || !scale.majorTickItems || scale.majorTickItems.length == 0)
        return;
      var direction:Number = (scale.orientation == "clockwise")?-1:1;

      var ox:Number = getOrigin().x;
      var oy:Number = getOrigin().y;
       
      var i:int;              
      var tickCount:Number;
      var tw:Number;
      var th:Number;
      var instances:Array;
      var ticks:Array;     
      var rad:Number;
      var tickStyleName:String;
      var tickIndex:uint;
      
      var showMajorTicksStyle:Object = getStyle("showMajorTicks");
      var showMajorTicks:Boolean = showMajorTicksStyle != false &&
        showMajorTicksStyle != "false"; 

      var showMinorTicksStyle:Object = getStyle("showMinorTicks");
      var showMinorTicks:Boolean = showMinorTicksStyle != false &&
        showMinorTicksStyle != "false"; 

      // major ticks
      rad = getRadius();         
      tw = getMajorTickWidth();
      th = getMajorTickLength();

      var col:* = getStyle("backgroundColor");
      if (col !== undefined)
      {
        var alp:Number = getStyle("backgroundAlpha");
        var wid:Number= Math.max(getMajorTickLength(), getMinorTickLength());
        drawColorTrack(graphics, ox, oy, rad, scale.orientation,
                       tickPlacement, startAngleRadians, endAngleRadians, wid, wid, col, alp); 
      }

      if (showMajorTicks)
      {
        ticks = scale.majorTickItems;
        tickCount = ticks.length;      
        tickStyleName = getStyle("majorTickStyleName");  
        if (_majorTicksCache == null) {
          _majorTicksCache = new InstanceCache(majorTickRenderer, _ticksHolder);
        } else {
          _majorTicksCache.factory = majorTickRenderer;
        }
        _majorTicksCache.count = tickCount;
        instances = _majorTicksCache.instances;                  
        for (i = 0; i < tickCount; i++) { 
          instances[i].styleName = tickStyleName;     
          positionTick(instances[i], ticks[i], tw, th, ox, oy,
                       direction, totalAngleRadians, tickPlacement, rad);
        }
      }
      else
      {
        if (_majorTicksCache == null)
          _majorTicksCache = new InstanceCache(majorTickRenderer, _ticksHolder);
        _majorTicksCache.count = 0;
      }
      
      if (showMinorTicks)
      {
        ticks = scale.minorTickItems;
        if (ticks && ticks.length > 0)
        {
          // minor ticks
          tw = getMinorTickWidth();
          th = getMinorTickLength();
                      
          tickCount = ticks.length;     
          tickStyleName = getStyle("minorTickStyleName");           
          if (_minorTicksCache == null) {
            _minorTicksCache = new InstanceCache(minorTickRenderer, _ticksHolder);
          } else {
            _minorTicksCache.factory = minorTickRenderer;
          }
          _minorTicksCache.count = tickCount;
          instances = _minorTicksCache.instances;           
          for (i = 0; i < tickCount; i++) {
            instances[i].styleName = tickStyleName;
            positionTick(instances[i], ticks[i], tw, th, ox, oy,
                         direction, totalAngleRadians, tickPlacement, rad);
          }
        }
      }
      else
      {
        if (_minorTicksCache == null) {
          _minorTicksCache = new InstanceCache(minorTickRenderer, _ticksHolder);
        }
        _minorTicksCache.count = 0;
      }
      var showLabelsStyle:Object = getStyle("showLabels");
      var showLabels:Boolean = showLabelsStyle != false &&
        showLabelsStyle != "false"; 

      var onlyMinMaxStyle:Object = getStyle("onlyMinMaxLabels");
      var onlyMinMax:Boolean = onlyMinMaxStyle != false &&
        onlyMinMaxStyle != "false"; 


      if (showLabels == false)
        _labelCache.count = 0;
      else
      {
        ticks = scale.majorTickItems;
        var lrad:Number = getLabelRadius();
        createLabels(ticks, onlyMinMax);
        var labels:Array = _labelCache.instances;
        
        var label:IFlexDisplayObject;
        var angle:Number;
        
        for (i = 0; i < labels.length; i++)
        {
          label = labels[i];
          if (_labelRenderer == null)
          {
            var tf:UITextField = UITextField(label);
            if (tf.text == "")
              continue;
            var format:TextFormat = tf.getTextStyles();
            format.size = getLabelFontSize();
            var tmp:String = tf.text;
            tf.text = "";
            tf.text = tmp;
            tf.setTextFormat(format);
          }
          tickIndex = i;
          if (onlyMinMaxStyle && i == 1)
            tickIndex = ticks.length - 1;
          if (tickIndex > ticks.length - 1)
            return;
  
          angle = startAngleRadians + direction * ticks[tickIndex].position * totalAngleRadians;
          
          tw = label.measuredWidth;
          th = label.measuredHeight;
          label.setActualSize(tw, th);
          positionLabel(label, ox, oy, lrad, tw, th, angle);
        }
      }
      if (_clipTicks) {

        var startAnglePoint:Point = new Point();
        var majorTickRad:Number;
        var minorTickRad:Number;        
        startAnglePoint.x = ox + Math.cos(startAngleRadians) * rad;
        startAnglePoint.y = oy - Math.sin(startAngleRadians) * rad;
        var mask:FlexShape;
        if (_ticksHolder.mask == null)
        {
          mask = new FlexShape();
          mask.name = "mask";
          _ticksHolder.mask = mask;        
          _ticksHolder.addChild(mask);
        }
        else
          mask = FlexShape(_ticksHolder.mask);
        var g:Graphics = mask.graphics;
        g.clear();
        g.beginFill(0xFF0000, 0.5);
        g.moveTo(ox, oy);
        g.lineTo(startAnglePoint.x, startAnglePoint.y);
        GraphicsUtilities.drawArc(g, ox, oy, startAngleRadians, direction * totalAngleRadians, 2*rad, NaN, true);
        g.lineTo(ox, oy);
        g.endFill();
      } else if (_ticksHolder.mask != null) {
        _ticksHolder.removeChild(_ticksHolder.mask);
        _ticksHolder.mask = null;
      }
    }

    /**
     *  @private
     */
    private function positionLabel(tf:IFlexDisplayObject, ox:Number, oy:Number, lrad:Number, tw:Number, th:Number, angle:Number):void
    {
        var ip:Point = new Point();
        var intersections:Array = [];        
        // Always start by placing textfield in "cross" position        
        tf.x = ox + Math.cos(angle) * lrad - tw / 2;
        tf.y = oy - Math.sin(angle) * lrad - th / 2;
        
        
        // A textfield position must verify following constraints:
        //   - textfield's center on the reference radius
        //   - if "inside", outside intersection point on reference circle
        //   - if "outside", inside intersection point on reference circle
        if (labelPlacement == "outside" || labelPlacement == "inside")
        {
          // Compute intersections points
          var side:Number; 

          // Intersection with top segment
          side = tf.x;
          ip.x = side;
          ip.y = -Math.tan(angle) * side + oy + Math.tan(angle) * ox;
          // Verify if intersection is on segment
          if (ip.y >= tf.y && ip.y <= tf.y + th)
            intersections.push(ip.clone());

          // Intersection with bottom segment
          side = tf.x + tw;
          ip.x = side;
          ip.y = -Math.tan(angle) * side + oy + Math.tan(angle) * ox;
          // Verify if intersection is on segment
          if (ip.y >= tf.y && ip.y <= tf.y + th)
            intersections.push(ip.clone());

          // Intersection with left segment
          side = tf.y;
          ip.x = -1/Math.tan(angle) * side + ox + 1/Math.tan(angle) * oy;
          ip.y = side;
          // Verify if intersection is on segment
          if (ip.x >= tf.x && ip.x <= tf.x + tw)
            intersections.push(ip.clone());

          // Intersection with right segment
          side = tf.y + th;
          ip.x = -1/Math.tan(angle) * side + ox + 1/Math.tan(angle) * oy;
          ip.y = side;
          // Verify if intersection is on segment
          if (ip.x >= tf.x && ip.x <= tf.x + tw)
            intersections.push(ip.clone());

          
          var dif:Number;
          if (labelPlacement == "inside")
          {
            for each (ip in intersections)
              {
                dif = distance(ip.x, ip.y, ox, oy) - lrad;
                if (dif >= 0)
                {
                  // Place reference intersection point on reference circle
                  tf.x = ox + Math.cos(angle) * (lrad - dif) - tw / 2;
                  tf.y = oy - Math.sin(angle) * (lrad - dif) - th / 2;
                  break;
                }
              }
          }          
          else // "outside" placement
          {
            for each (ip in intersections)
              {
                dif = distance(ip.x, ip.y, ox, oy) - lrad;
                if (dif <= 0)
                {
                  // Place reference intersection point on reference circle
                  tf.x = ox + Math.cos(angle) * (lrad - dif) - tw / 2;
                  tf.y = oy - Math.sin(angle) * (lrad - dif) - th / 2;
                  break;
                }
              }
          }
        }      
    }

    /**
     *  @private 
     */       
    private function positionTick(tickRenderer:DisplayObject, tick:TickItem,
                                  cWidth:Number, cHeight:Number,
                                  ox:Number, oy:Number,
                                  direction:Number, totalAngle:Number,
                                  placement:String, radius:Number):void 
    { 
      if (tickRenderer is IDataRenderer) 
        IDataRenderer(tickRenderer).data = tick;
      
      var angle:Number = MathUtil.mod2PI(MathUtil.toRadians(scale.startAngle)) + direction* tick.position * totalAngle;
  
      var ix:Number = ox + Math.cos(angle) * radius;
      var iy:Number = oy - Math.sin(angle) * radius;
      tickRenderer.rotation = 90 - MathUtil.toDegrees(angle);
      tickRenderer.width = cWidth;
      tickRenderer.height = cHeight;
      
      tickRenderer.x = ix;
      tickRenderer.y = iy;
      var centerPoint:Point;
      var centerAngle:Number;
      switch(placement) {
      case "inside": 
        centerPoint = new Point(tickRenderer.width/2, 0);
        centerAngle = Math.PI / 2; 
        break;
      case "cross": 
        centerPoint = new Point(tickRenderer.width/2, tickRenderer.height/2);
        centerAngle = Math.atan(tickRenderer.width/tickRenderer.height);
        break;                    
      case "outside": 
        centerPoint = new Point(tickRenderer.width/2, tickRenderer.height);
        centerAngle = Math.atan((tickRenderer.width/2)/tickRenderer.height);
        break;
      }
      centerAngle = Math.PI - centerAngle;
      var trans:Point;
      trans = polar(ix, iy, angle-centerAngle, centerPoint.length);
      tickRenderer.x -= trans.x - ix;
      tickRenderer.y -= trans.y - iy;

    }
    
    /**
     *  @private 
     */ 
    private function drawColorTrack(g:Graphics, ox:Number, oy:Number, 
                                    radius:Number, orientation:String,
                                    placement:String,  
                                    startAngleRadians:Number, endAngleRadians:Number, 
                                    sWeight:Number, eWeight:Number, 
                                    color:uint, alpha:Number):void
    {
      var angleStep:Number = Math.PI / 360;
      var totalAngle:Number;
      totalAngle = computeAngle(startAngleRadians, endAngleRadians, orientation);

      var iterCount:Number = int(totalAngle / angleStep);      
      
      var innerRadius:Number;
      var outerRadius:Number;
      var outerStep:Number = 0;
      var innerStep:Number = 0;
      var px:Number;
      var py:Number;

      switch (placement)
      {
      case "inside": 
        innerRadius = - sWeight ; 
        outerRadius = 0; 
        innerStep = (sWeight - eWeight) / iterCount;
        break;
      case "outside": 
        innerRadius = 0; 
        outerRadius = sWeight; 
        outerStep =  -(sWeight - eWeight) / iterCount;
        break;
      case "cross": 
        innerRadius = -sWeight/2; 
        outerRadius = sWeight/2; 
        outerStep = -(sWeight - eWeight) / iterCount / 2;
        innerStep = (sWeight - eWeight) / iterCount / 2;          
        break;
      }

      var angle:Number;
      var i:int;
      if (orientation == "clockwise")
        angleStep = -angleStep;

      graphics.clear();

      g.beginFill(color, alpha);

      px = ox + Math.cos(startAngleRadians) * (radius + innerRadius);
      py = oy - Math.sin(startAngleRadians) * (radius + innerRadius);
      g.moveTo(px, py);
      for (i = 1; i < iterCount; i++)
      {
        angle = startAngleRadians + i * angleStep;
        px = ox + Math.cos(angle + angleStep) * (radius + innerRadius + i * innerStep);
        py = oy - Math.sin(angle + angleStep) * (radius + innerRadius + i * innerStep);
        g.lineTo(px, py);
      }
      px = ox + Math.cos(angle + angleStep) * (radius + outerRadius + (iterCount-1) * outerStep);
      py = oy - Math.sin(angle + angleStep) * (radius + outerRadius + (iterCount-1) * outerStep);
      g.lineTo(px, py);
      for (i = iterCount-1; i >= 0; i--)
      {
        angle = startAngleRadians + i * angleStep;
        px = ox + Math.cos(angle + angleStep) * (radius + outerRadius + i * outerStep);
        py = oy - Math.sin(angle + angleStep) * (radius + outerRadius + i * outerStep);
        g.lineTo(px, py);
      }
      px = ox + Math.cos(startAngleRadians) * (radius + innerRadius);
      py = oy - Math.sin(startAngleRadians) * (radius + innerRadius);
      g.lineTo(px, py);
    }

    /**
     *  @private
     */
    override protected function measure():void
    {
      super.measure();
      if (measuredWidth == 0 ||
          isNaN(labelFontSize) ||
          isNaN(labelRadius) ||
          isNaN(majorTickLength) ||
          isNaN(majorTickWidth) ||
          isNaN(minorTickLength) ||
          isNaN(minorTickWidth))
      {
        measuredMinWidth = measuredMinHeight = measuredWidth = measuredHeight = 0;        
      }
      else
      {
        var rad:Number = radius;
        var direction:Number = (scale.orientation == "clockwise")?-1:1;
        var pts:Array = [];
        var insts:Array;
        var tick:TickItem;                  
        var angle:Number;
        var i:int;
        var cPoint:Point; // center
        var bb:Rectangle;
        
        // major ticks bounds
        switch(tickPlacement)
        {
          case "cross": rad += majorTickLength / 2; break;
          case "outside": rad += majorTickLength; break;
        }
            
        insts = scale.majorTickItems;
        for (i = 0; i < insts.length; i++)
        { 
          tick = insts[i];
          angle = startAngleRadians + direction * tick.position * totalAngleRadians;
          cPoint = polar(originX, originY, angle, rad);
          pts.push(polar(cPoint.x, cPoint.y, angle+Math.PI/2, majorTickWidth/2));
          pts.push(polar(cPoint.x, cPoint.y, angle-Math.PI/2, majorTickWidth/2));
          cPoint = polar(originX, originY, angle, rad-majorTickLength);
          pts.push(polar(cPoint.x, cPoint.y, angle+Math.PI/2, majorTickWidth/2));
          pts.push(polar(cPoint.x, cPoint.y, angle-Math.PI/2, majorTickWidth/2));

        }
        
        // minor ticks bounds 
        switch(tickPlacement)
        {
          case "cross": rad += majorTickLength / 2; break;
          case "outside": rad += majorTickLength; break;
        }        
         
        insts = scale.minorTickItems;
        for (i = 0; i < insts.length; i++)
        { 
          tick = insts[i];
          angle = startAngleRadians + direction * tick.position * totalAngleRadians;
          cPoint = polar(originX, originY, angle, rad);
          pts.push(polar(cPoint.x, cPoint.y, angle+Math.PI/2, minorTickWidth/2));
          pts.push(polar(cPoint.x, cPoint.y, angle-Math.PI/2, minorTickWidth/2));
          cPoint = polar(originX, originY, angle, rad-minorTickLength);
          pts.push(polar(cPoint.x, cPoint.y, angle+Math.PI/2, minorTickWidth/2));
          pts.push(polar(cPoint.x, cPoint.y, angle-Math.PI/2, minorTickWidth/2));
        }
        // labels bounds
        if (getStyle("showLabels") == true)
        {
          var lF:Function = labelFunction != null ? labelFunction : defaultLabelFunction;        
          var measuringLabel:UITextField = new UITextField();
          var ticks:Array;
          if (getStyle("onlyMinMaxLabels") == true)
          {
            ticks = [];
            ticks.push(scale.majorTickItems[0]);
            ticks.push(scale.majorTickItems[scale.majorTickItems.length-1]);
          }
          else
          {
            ticks = scale.majorTickItems;
          }
          var tw:Number;
          var th:Number;
          for (i = 0; i < ticks.length; i++)
          {
            var format:TextFormat = measuringLabel.getTextStyles();
            format.size = labelFontSize;
            measuringLabel.text = lF(ticks[i]);
            measuringLabel.setTextFormat(format);
            angle = startAngleRadians + direction * ticks[i].position * totalAngleRadians;
            
            tw = measuringLabel.measuredWidth + 2;
            th = measuringLabel.measuredHeight;
            measuringLabel.setActualSize(tw, th);
            positionLabel(measuringLabel, originX, originY, labelRadius, tw, th, angle);
            pts.push(new Point(measuringLabel.x, measuringLabel.y));
            pts.push(new Point(measuringLabel.x + measuringLabel.width, measuringLabel.y));
            pts.push(new Point(measuringLabel.x + measuringLabel.width, measuringLabel.y + measuringLabel.height));
            pts.push(new Point(measuringLabel.x, measuringLabel.y + measuringLabel.height));
          }
        }
        
        bb = RectangleUtil.bbox(pts);
        measuredMinWidth = measuredWidth = bb.x + bb.width;
        measuredMinHeight = measuredHeight = bb.y + bb.height;
      }    
    }
  }
}
